package com.srv.client.widget;

import com.google.gwt.cell.client.AbstractEditableCell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TextAreaElement;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.text.shared.SafeHtmlRenderer;
import com.sencha.gxt.widget.core.client.info.Info;
import com.srv.shared.Control;

public class TextAreaEditCell extends AbstractEditableCell<Control, TextAreaEditCell.ViewData> {

	/**
	 * The view data object used by this cell. We need to store both the text and
	 * the state because this cell is rendered differently in edit mode. If we did
	 * not store the edit state, refreshing the cell with view data would always
	 * put us in to edit state, rendering a text box instead of the new text
	 * string.
	 */
	static class ViewData {

		private boolean isEditing;

		/**
		 * If true, this is not the first edit.
		 */
		private boolean isEditingAgain;

		/**
		 * Keep track of the original value at the start of the edit, which might be
		 * the edited value from the previous edit and NOT the actual value.
		 */
		private Control original;

		private Control control;

		/**
		 * Construct a new ViewData in editing mode.
		 *
		 * @param text the text to edit
		 */
		public ViewData(Control control) {
			this.original = control;
			this.control = control;
			this.isEditing = true;
			this.isEditingAgain = false;
			//Info.display("", control.getText());
		}

		@Override
		public boolean equals(Object o) {
			if (o == null) {
				return false;
			}
			ViewData vd = (ViewData) o;
			return equalsOrBothNull(original, vd.original)
					&& equalsOrBothNull(control, vd.control) && isEditing == vd.isEditing
					&& isEditingAgain == vd.isEditingAgain;
		}

		public Control getOriginal() {
			return original;
		}

		public Control getControl() {
			return control;
		}

		@Override
		public int hashCode() {
			return original.hashCode() + control.hashCode()
					+ Boolean.valueOf(isEditing).hashCode() * 29
					+ Boolean.valueOf(isEditingAgain).hashCode();
		}

		public boolean isEditing() {
			return isEditing;
		}

		public boolean isEditingAgain() {
			return isEditingAgain;
		}

		public void setEditing(boolean isEditing) {
			boolean wasEditing = this.isEditing;
			this.isEditing = isEditing;

			// This is a subsequent edit, so start from where we left off.
			if (!wasEditing && isEditing) {
				isEditingAgain = true;
				original = control;
			}
		}

		public void setControl(Control control) {
			this.control = control;
		}

		public void setControlText(String text) {
			control.setText(text);
		}

		private boolean equalsOrBothNull(Object o1, Object o2) {
			return (o1 == null) ? o2 == null : o1.equals(o2);
		}

//		public void setChecked() {
//			if (control.getValue().equals("unchecked"))
//				control.setValue("checked");
//			else
//				control.setValue("unchecked");
//		}

		public void setChecked(boolean checked) {
			if (checked)
				control.setValue("checked");
			else
				control.setValue("unchecked");
			}

	}

	private String name;

	/**
	 * Construct a new EditTextCell that will use a given
	 * {@link SafeHtmlRenderer} to render the value when not in edit mode.
	 * 
	 * @param renderer
	 *            a {@link SafeHtmlRenderer SafeHtmlRenderer<String>} instance
	 */
	public TextAreaEditCell(String name) {
		super("change", "click", "keyup", "keydown", "blur");

		this.name = name;
	}

	@Override
	public boolean isEditing(Context context, Element parent, Control value) {
		ViewData viewData = getViewData(context.getKey());
		return viewData == null ? false : viewData.isEditing();
	}

	@Override
	public void onBrowserEvent(Context context, Element parent, Control control,
			NativeEvent event, ValueUpdater<Control> valueUpdater) {
		Object key = context.getKey();
		ViewData viewData = getViewData(key);

		EventTarget eventTarget = event.getEventTarget();
		
		if (Element.is(eventTarget)) {
			final String type = event.getType();
			final Element target = Element.as(eventTarget);
			final String element = target.getTagName().toLowerCase();
			if ("input".equals(element)) {
				if ("change".equals(type)) {
					
					if (viewData == null) {
						viewData = new ViewData(control);
						setViewData(key, viewData);
					}
					viewData.setEditing(false);
					
					InputElement input = getInputElement(parent);
					final boolean checked = input.isChecked();
					viewData.setChecked(checked); 
					
					if (valueUpdater != null) {
						valueUpdater.update(viewData.getControl());
					}
					
				}
			} else {
			
				if (viewData != null && viewData.isEditing())
					// Handle the edit event.
					editEvent(context, parent, control, viewData, event, valueUpdater);
	
				else {
					if ("click".equals(type) 
							//|| 
							//("keyup".equals(type) && event.getKeyCode() == KeyCodes.KEY_ENTER)
							) {
						
						// Go into edit mode.
						if (viewData == null) {
							viewData = new ViewData(control);
							setViewData(key, viewData);
						} else {
							viewData.setEditing(true);
						}

						
						//Convert the cell to edit mode.
						setValue(context, parent, control);
						TextAreaElement input = getTextAreaElement(parent);
						input.focus();
						//input.select();
					}	
				}
			}
		}

	}
	
	@Override
	public void render(Context context, Control control, SafeHtmlBuilder sb) {
		// Get the view data.
		Object key = context.getKey();
		ViewData viewData = getViewData(key);
		if (viewData != null && !viewData.isEditing() && control != null
				&& control.equals(viewData.getControl())) {
			clearViewData(key);
			viewData = null;
		}

		if (viewData != null) {
			sb.appendHtmlConstant(viewData.getControl().getHtmlEdit("text"));

//			if (viewData.isEditing()) { 
//				// Note the template will not treat SafeHtml specially
//			} else {
//				// The user pressed enter, but view data still exists.
//			}
			
		} else if (control != null) {
			//at beginning
			sb.appendHtmlConstant(control.getControl().getHtml("text"));
		}
	}

	@Override
	public boolean resetFocus(Context context, Element parent, Control control) {
		if (isEditing(context, parent, control)) {
			getTextAreaElement(parent).focus();
			return true;
		}
		return false;
	}

	/**
	 * Clear selected from the input element. Both Firefox and IE fire spurious
	 * onblur events after the input is removed from the DOM if selection is not
	 * cleared.
	 *
	 * @param input the input element
	 */
	private native void clearInput(Element input) /*-{
        if (input.selectionEnd)
            input.selectionEnd = input.selectionStart;
        else if ($doc.selection)
            $doc.selection.clear();
    }-*/;

	/**
	 * Commit the current value.
	 *
	 * @param context      the context of the cell
	 * @param parent       the parent Element
	 * @param viewData     the {@link ViewData} object
	 * @param valueUpdater the {@link ValueUpdater}
	 */
	private void commit(Context context, Element parent, ViewData viewData,
			ValueUpdater<Control> valueUpdater) {
		Control control = updateViewData(parent, viewData, false);
		clearInput(getTextAreaElement(parent));
		setValue(context, parent, viewData.getOriginal());
		if (valueUpdater != null) {
			valueUpdater.update(control);
		}
	}

	private void editEvent(Context context, Element parent, Control control,
			ViewData viewData, NativeEvent event, ValueUpdater<Control> valueUpdater) {
		String type = event.getType();
		boolean keyUp = "keyup".equals(type);
		boolean keyDown = "keydown".equals(type);
		if (keyUp || keyDown) {
			int keyCode = event.getKeyCode();
			if (keyUp && keyCode == KeyCodes.KEY_ENTER) {
				// Commit the change.
				commit(context, parent, viewData, valueUpdater);
			} else 
				if (keyUp && keyCode == KeyCodes.KEY_ESCAPE) {
					// Cancel edit mode.
					//				String originalText = viewData.getOriginal();
					Control originalControl = viewData.getOriginal();
//					Info.display("", originalControl.getText());
					if (viewData.isEditingAgain()) {
						viewData.setControl(originalControl);
						viewData.setEditing(false);
					} else {
						setViewData(context.getKey(), null);
					}
					
					// Convert the cell to non-edit mode.
					clearInput(getTextAreaElement(parent));
					setValue(context, parent, control);
				} else {
					// Update the text in the view data on each key.
					updateViewData(parent, viewData, true);
				}		
		} else if ("blur".equals(type)) {
			// Commit the change. Ensure that we are blurring the input element
			// and
			// not the parent element itself.
			EventTarget eventTarget = event.getEventTarget();
			if (Element.is(eventTarget)) {
				Element target = Element.as(eventTarget);
				if ("textarea".equals(target.getTagName().toLowerCase())) {
					commit(context, parent, viewData, valueUpdater);
				}
			}
		}
	}

	
	/**
	 * Get the checkbox input element from the parent element that wraps our
	 * cell.
	 * 
	 * @param parent the parent element
	 * @return the checkbox
	 */
	private InputElement getInputElement(Element parent) {
		// We need to navigate down to our input element.
		final TableElement table = parent.getFirstChildElement().cast();
		final TableRowElement tr = table.getRows().getItem(0);
		final TableCellElement td = tr.getCells().getItem(0);
		final InputElement input = td.getFirstChildElement().cast();
		return input;
	}	
	
	/**
	 * Get the input element in edit mode.
	 */
	private TextAreaElement getTextAreaElement(Element parent) {
		final TableElement table = parent.getFirstChild().<TableElement> cast();
		final TableRowElement tr = table.getRows().getItem(0);
		final TableCellElement td = tr.getCells().getItem(1);
		final TextAreaElement textArea = td.getFirstChildElement().cast();
		return textArea;
	}

	/**
	 * Update the view data based on the current value.
	 *
	 * @param parent    the parent element
	 * @param viewData  the {@link ViewData} object to update
	 * @param isEditing true if in edit mode
	 * @return the new value
	 */
	private Control updateViewData(Element parent, ViewData viewData,
			boolean isEditing) {
		TableElement table = parent.getFirstChild().<TableElement> cast();
		TableRowElement tr = table.getRows().getItem(0);
		TableCellElement td = tr.getCells().getItem(1);
		TextAreaElement textArea = td.getFirstChildElement().cast();

		String text = textArea.getValue();
		viewData.setControlText(text); 
		viewData.setEditing(isEditing);
		return viewData.getControl();
	}

}		
